package com.yeskery.nut.core;

import com.yeskery.nut.http.BasicCookie;
import com.yeskery.nut.http.BasicSession;
import com.yeskery.nut.util.StringUtils;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * Nut 默认实现 {@link SessionManager} 接口的实现类
 * @author sprout
 * 2019-03-16 14:57
 * @version 1.0
 *
 * @see com.yeskery.nut.core.SessionManager
 */
public final class BasicSessionManager implements SessionManager {

	/** SessionId 的格式正则 */
	private static final Pattern SESSION_ID_PATTERN = Pattern.compile("[a-z0-9]{32}");

	/** Session 最小过期时间，单位为分钟 */
	private static final int MIN_EXPIRE = 5;

	/** {@link SessionManager} */
	private static volatile SessionManager sessionManager;

	/** Session 默认过期时间，单位为分钟 */
	private transient int expire = 30;

	/** 用于保存Session的载体 */
	private final Map<String, SessionSet> sessionSetMap;

	/**
	 * 私有化构造方法
	 * @param expire Session 过期时间
	 */
	private BasicSessionManager(ThreadPool threadPool, int expire) {
		this(threadPool);
		this.expire = expire;
	}

	/**
	 * 私有化构造方法
	 */
	private BasicSessionManager(ThreadPool threadPool) {
		sessionSetMap = new ConcurrentHashMap<>();

		// 开启定时管理session是否过期的线程任务
		threadPool.getScheduledThreadPool().schedule(() -> {
			LocalDateTime now = LocalDateTime.now();
			sessionSetMap.entrySet().removeIf(item -> now.isAfter(item.getValue().expireTime));
		}, 1, TimeUnit.MINUTES);
	}

	/**
	 * 以单例模式获取当前的 {@link SessionManager}
	 * @param threadPool 线程池对象
	 * @param expire Session 过期时间
	 * @return 单例模式的 {@link SessionManager}
	 */
	public static SessionManager getInstance(ThreadPool threadPool, int expire) {
		if (sessionManager == null) {
			synchronized (BasicSessionManager.class) {
				if (sessionManager == null) {
					sessionManager = new BasicSessionManager(threadPool, Math.max(expire, MIN_EXPIRE));
				}
			}
		}
		return sessionManager;
	}

	/**
	 * 以单例模式获取当前的 {@link SessionManager}
	 * @param threadPool 线程池对象
	 * @return 单例模式的 {@link SessionManager}
	 */
	public static SessionManager getInstance(ThreadPool threadPool) {
		if (sessionManager == null) {
			synchronized (BasicSessionManager.class) {
				if (sessionManager == null) {
					sessionManager = new BasicSessionManager(threadPool);
				}
			}
		}
		return sessionManager;
	}

	@Override
	public int getExpire() {
		return expire;
	}

	@Override
	public Session getSession(String sessionId) {
		SessionSet sessionSet = sessionSetMap.get(sessionId);
		//如果 SessionId 不存在，则创建一个新的 Session 环境
		if (sessionSet == null) {
			sessionSet = new SessionSet(LocalDateTime.now().plusMinutes(expire), new BasicSession(sessionId));
			sessionSetMap.put(sessionId, sessionSet);
		} else {
			//如果 SessionId 存在，则刷新 Session 的过期时间
			sessionSet.flushExpireTime();
		}
		return sessionSet.session;
	}

	@Override
	public String getSessionId(Request request) {
		//尝试从request空间中获取SessionId
		String sessionId = (String) request.getAttribute(SessionManager.SESSION_NAME);
		//如果没有获取到，尝试从Cookie中获取SessionId
		if (StringUtils.isEmpty(sessionId)) {
			for (Cookie cookie : request.getCookies()) {
				if (SessionManager.SESSION_NAME.equals(cookie.getName())) {
					sessionId = cookie.getValue();
				}
			}
		}
		//如果没有获取到，尝试从参数中获取SessionId
		if (StringUtils.isEmpty(sessionId)) {
			sessionId = request.getParameter(SessionManager.SESSION_NAME);
		}
		//如果没有获取到，则重新创建SessionId
		if (StringUtils.isEmpty(sessionId)) {
			return UUID.randomUUID().toString().replaceAll("-", "");
		} else {
			return sessionId;
		}
	}

	@Override
	public boolean hasSession(String sessionId) {
		return sessionSetMap.containsKey(sessionId);
	}

	@Override
	public String addSession(Session session) {
		String sessionId = UUID.randomUUID().toString().replaceAll("-", "");
		sessionSetMap.put(sessionId, new SessionSet(LocalDateTime.now().plusMinutes(expire), new BasicSession(sessionId)));
		return sessionId;
	}

	@Override
	public void initSessionEnv(Request request, Response response) {
		String sessionId = sessionManager.getSessionId(request);
		//如果SessionId不存在，则在Cookie中增加SessionId
		if (!sessionManager.hasSession(sessionId)) {
			//如果SessionId不符合默认的生成规则，则重新生成一个SessionId
			boolean needSetCookie = !SESSION_ID_PATTERN.matcher(sessionId).matches();
			//如果Cookie里没有SessionId，则重新生成一个SessionId
			if (!request.hasCookie(SessionManager.SESSION_NAME)) {
				needSetCookie = true;
			}
			if (needSetCookie) {
				sessionId = UUID.randomUUID().toString().replaceAll("-", "");
				BasicCookie cookie = new BasicCookie(SessionManager.SESSION_NAME, sessionId);
				cookie.setHttponly(false);
				response.addCookie(cookie);
			}
		}
		//将SessionId存入到Request域中
		request.addAttribute(SessionManager.SESSION_NAME, sessionId);
	}

	/**
	 * Session 的内部存储类，用来存储 session 对象和 session 的过期时间
	 * @author sprout
	 * 2019-03-16 14:57
	 * @version 1.0
	 */
	class SessionSet {
		/** Session 的过期时间 */
		private LocalDateTime expireTime;

		/** Session对象 */
		private final Session session;

		/**
		 * 构建一个Session 对象
		 * @param expireTime Session 的过期时间
		 * @param session Session对象
		 */
		SessionSet(LocalDateTime expireTime, Session session) {
			this.expireTime = expireTime;
			this.session = session;
		}

		/**
		 * 刷新Session的过期时间
		 */
		void flushExpireTime() {
			expireTime = expireTime.plusMinutes(BasicSessionManager.this.expire);
		}
	}
}
